/*
 I need a SwiftUI replacement for a macOS objective-c view controller that displays a variable number of vertical groups of buttons (of an NSButton subclass). Each group of buttons has an optional label that functions as a title (or group header). The buttons within a group are loosely organized in rows and columns and not all spaces need to be populated. Unpopulated spaces may remain as negative space or an adjacent button could utilize the space (for example, in a group with generally two columns if a particular row has only column populated with a button that button could span both columns).  When the view is in editing mode, the user can change the number of rows and columns, but when not in editing mode the layout should be fixed. When in editing mode, the user should be able to reorder the groups by dragging, and additionally reorder the buttons by dragging, both within a group and in between groups.

 The new SwiftUI code must be useable within an existing objective-c macOS app. Please provide an integration example.
 */

# ButtonGridView — SwiftUI Button Grid for macOS (Objective-C host apps)

A SwiftUI component that displays a variable number of vertical groups of
`NSButton` (or subclass) instances arranged in a configurable grid, with a
full drag-and-drop edit mode. Designed to drop into an existing Objective-C
macOS application with minimal friction.

---

## File Overview

| File | Purpose |
|---|---|
| `ButtonGridModels.swift` | `ButtonCell`, `ButtonGroup`, `ButtonGridViewModel` |
| `NSButtonWrapper.swift` | `NSViewRepresentable` bridge for any `NSButton` subclass |
| `ButtonGridDragDrop.swift` | `Transferable` payloads for cell and group drag-and-drop |
| `ButtonGridCellView.swift` | Single cell view (button or empty slot) + drop target |
| `ButtonGroupView.swift` | One group: header, size editor, row/column grid |
| `ButtonGridContainerView.swift` | Root `ScrollView` + toolbar (`isEditing` toggle, add group) |
| `ObjCBridge.swift` | `BGContainerViewController`, `BGButtonGroupProxy`, `BGButtonCellProxy` |
| `MyLegacyViewController.m` | **Integration example** — start here |

---

## Requirements

- macOS 13 Ventura or later (uses `draggable(_:)` / `dropDestination` SwiftUI APIs)
- Swift 5.9 +
- Xcode 15 +

---

## Quick Start

### 1. Add files to your Xcode target

Add all six Swift files. Xcode will offer to create a bridging header —
accept. Leave the header empty; it just needs to exist.

### 2. Import the generated Swift header in your Obj-C file

```objc
#import "YourTargetName-Swift.h"
```

The module name is your target's **Product Module Name** build setting
(spaces replaced with underscores). You can verify it in
*Build Settings → Swift Compiler — General → Product Module Name*.

### 3. Register exported UTTypes (drag-and-drop)

The drag-and-drop payloads declare two private UTTypes:
- `com.yourapp.buttoncell-drag`
- `com.yourapp.buttongroup-drag`

Add them to your `Info.plist` under `UTExportedTypeDeclarations`, or
replace the identifier strings in `ButtonGridDragDrop.swift` with your
own reverse-DNS identifiers.

Minimal `Info.plist` entries:

```xml
<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeIdentifier</key>
        <string>com.yourapp.buttoncell-drag</string>
        <key>UTTypeConformsTo</key>
        <array><string>public.data</string></array>
    </dict>
    <dict>
        <key>UTTypeIdentifier</key>
        <string>com.yourapp.buttongroup-drag</string>
        <key>UTTypeConformsTo</key>
        <array><string>public.data</string></array>
    </dict>
</array>
```

### 4. Embed the view controller

```objc
BGContainerViewController *vc =
    [[BGContainerViewController alloc] initWithIsEditing:NO];

[self addChildViewController:vc];
[self.view addSubview:vc.view];

vc.view.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:@[
    [vc.view.leadingAnchor  constraintEqualToAnchor:self.view.leadingAnchor],
    [vc.view.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
    [vc.view.topAnchor      constraintEqualToAnchor:self.view.topAnchor],
    [vc.view.bottomAnchor   constraintEqualToAnchor:self.view.bottomAnchor],
]];
```

### 5. Populate groups

```objc
BGButtonGroupProxy *group = [[BGButtonGroupProxy alloc]
                              initWithLabel:@"My Group"
                                    columns:2
                                       rows:2];

NSButton *btn = [MyCustomButton buttonWithTitle:@"Foo" target:self action:@selector(foo:)];
BGButtonCellProxy *cell = [[BGButtonCellProxy alloc] initWithButton:btn columnSpan:1];
[group setCell:cell row:0 col:0];

[vc addGroup:group];
```

### 6. Toggle editing from Obj-C

```objc
vc.isEditing = YES;   // enter edit mode
vc.isEditing = NO;    // exit edit mode
```

---

## Architecture

```
ButtonGridContainerView  (ScrollView + toolbar)
  └── ButtonGroupView  ×N  (group header, size editor in edit mode)
        └── RowCellsView  ×rows
              └── ButtonGridCellView  ×columns
                    └── NSButtonWrapper  (NSViewRepresentable)
```

### Data flow

```
ButtonGridViewModel  (@ObservedObject)
  ├── groups: [ButtonGroup]  (@Published)
  │     ├── label, columns, rows  (@Published)
  │     └── cells: [ButtonCell?]  (@Published)
  │           └── button: NSButton   (opaque, host-managed)
  └── isEditing: Bool  (@Published)
```

All mutation goes through `ButtonGridViewModel` methods or direct
`@Published` property writes, so SwiftUI re-renders automatically.

---

## Spanning buttons

A `ButtonCell` with `columnSpan: 2` in a 2-column group occupies the full
row width. Leave the adjacent slot(s) as `nil` — `RowCellsView` skips
them automatically during layout.

```objc
BGButtonCellProxy *wide = [[BGButtonCellProxy alloc]
                            initWithButton:bigBtn columnSpan:2];
[group setCell:wide row:0 col:0];
// col 1 in row 0 intentionally left empty
```

---

## Drag-and-drop in edit mode

| Action | How |
|---|---|
| Reorder groups | Drag the `≡` handle in the group header to another group's drop zone |
| Move a button within a group | Drag the button cell; drop on target slot (swaps) |
| Move a button between groups | Same drag; drop on a cell in a different group |
| Drop on empty slot | Button moves; source slot becomes empty |

Edit mode is activated by tapping the **Edit** button in the toolbar, or
by setting `vc.isEditing = YES` from Objective-C.

---

## Customisation hooks

- **Button sizing**: `NSButtonWrapper` uses `intrinsicContentSize`. Your
  subclass can override `intrinsicContentSize` to control dimensions.
- **Row height**: Falls back to 28 pt if `intrinsicContentSize.height ≤ 0`.
- **Column gap / row gap**: Change the `spacing:` parameters in
  `RowCellsView` and `ButtonGroupView.cellGrid`.
- **Group background / border**: Modify the `.background` modifier in
  `ButtonGroupView.body`.
- **Animation spring**: All animations use `.spring(response: 0.35)` —
  adjust in `ButtonGridContainerView`.
